#include <memory.h>
#include <stdio.h>

#define DUA_CORE

#include "dua_def.h"
#include "dua_opcode.h"
#include "dua_proto.h"

#define castNumber(TT, CAST, TYPE) \
    res = &slot[D->top ++]; v1 = &slot[cR1]; \
    res->type = (TYPE); \
    switch(v1->type){ \
        case DUA_TINT: res->TT = (CAST)v1->i; break; \
        case DUA_TLONG: res->TT = (CAST)v1->l; break; \
        case DUA_TFLOAT: res->TT = (CAST)v1->f; break; \
        case DUA_TDOUBLE: res->TT = (CAST)v1->d; break; \
    }

#define arithNumer(TT, ARITH) \
    v1 = &slot[cR1]; v2 = &slot[cR2]; \
    if(cFlag){ res = v1; }else{ res = &slot[D->top ++]; } \
    res->type = v1->type; res->TT = v1->TT ARITH v2->TT;

extern void
duaDo(DuaState* D, DuaClosure* closure){
    DuaVM* vm = D->vm;
    DuaProto* proto = closure->proto;
    DuaValue* k = proto->k;
    DuaValue* slot = D->slot;
    memcpy(slot, k, sizeof(DuaValue) * proto->kCount);
    int pc, code, cOp, cR1, cR2, cFlag;
    DuaValue* src, *res, *v1, *v2;
    pc = 0;
    for(;;){
        code = proto->code[pc];
        duaReadCode(code, c);
        switch(cOp){
        case OP_LOAD:
            slot[D->top++] = k[cR1];
            pc++; break;
        case OP_MOVE:
            slot[cR1] = slot[--D->top];
            pc++; break;
        case OP_COPY:
            slot[cR1] = slot[cR2];
            pc++; break;
        case OP_POP:
            D->top -= cR1;
            pc ++; break;
        case OP_CASTB:
            castNumber(b, int, DUA_TBOOL);
            pc++;  break;
        case OP_CASTI:
            castNumber(i, int32_t, DUA_TINT);
            pc++;  break;
        case OP_CASTL:
            castNumber(l, int64_t, DUA_TLONG);
            pc++; break;
        case OP_CASTF:
            castNumber(f, float, DUA_TFLOAT);
            pc++; break;
        case OP_CASTD:
            castNumber(d, double, DUA_TDOUBLE);
            pc++; break;
        case OP_ADDI:
            arithNumer(i, +);
            pc++; break;
        case OP_ADDL:
            arithNumer(l, +);
            pc++; break;
        case OP_ADDF:
            arithNumer(f, +);
            pc++; break;
        case OP_ADDD:
            arithNumer(d, +);
            pc++; break;
        case OP_SUBI:
            arithNumer(i, -);
            pc++; break;
        case OP_SUBL:
            arithNumer(l, -);
            pc++; break;
        case OP_SUBF:
            arithNumer(f, -);
            pc++; break;
        case OP_SUBD:
            arithNumer(d, -);
            pc++; break;
        case OP_MULI:
            arithNumer(i, *);
            pc++; break;
        case OP_MULL:
            arithNumer(l, *);
            pc++; break;
        case OP_MULF:
            arithNumer(f, *);
            pc++; break;
        case OP_MULD:
            arithNumer(d, *);
            pc++; break;
        case OP_DIVI:
            arithNumer(i, /);
            pc++; break;
        case OP_DIVL:
            arithNumer(l, / );
            pc++; break;
        case OP_DIVF:
            arithNumer(f, / );
            pc++; break;
        case OP_DIVD:
            arithNumer(d, / );
            pc++; break;
        case OP_JUMP:
            pc += (-1 * cFlag) * cR1;
            break;
        case OP_JUMPIF:
            src = &slot[cR1];
            if(src->b){
                pc += (-1 * cFlag) * cR2;
            }else{
                pc ++;
            }
            break;
        case OP_END:
            return;
        default:
            duaRet(D, DUA_ERROR, "unknow opcode");
            return;
        }
    }
}
